package edu.upenn.seas.peerreview.smui
import edu.upenn.seas.peerreview.{Id, Sink}
import scala.collection.immutable.Queue

import scala.actors.Actor._
import scala.actors.Actor

/* ********************************************************** */
// Battleship State Machine
// Same as BSSMActor.scala, but rpc-based


class RPCBSSM(logic: BattleshipLogic, myId:Id, transport: Sink.T, tournament:String) extends RPCAgent[BSSMInterface](myId, transport) {

  var uiId: Id = null
  var ui: BSUIInterface = null
  var myManagerId: Id = null
  var myManager: BSManagerInterface = null
  
  def rdebug(msg: String) = Debug.debug("rpc")("[%s] %s".format(myId, msg))

  val statemach = new (Id => (RPCProxy with BSSMInterface))  {
    val m = scala.collection.mutable.Map[Id, RPCProxy with BSSMInterface]()
    def apply(id: Id) = m.getOrElseUpdate(id, new RPCProxy(id) with BSSMInterface)
  }

  def main {
    //===Initialization

    respondToCall { case (from, 'linkUI, _) => uiId = from }
    ui = new RPCProxy(uiId) with BSUIInterface

    val coordinator = new RPCProxy(Id.coordinatorId) with NameServerInterface
    myManagerId = coordinator.get(tournament).get

    myManager = new RPCProxy(myManagerId) with BSManagerInterface
    myManager.joinServer

    def playGames() {
      myManager.getConfigs match {
        case Some(cfgs) => playGame(cfgs); playGames()
        case None => 
          rdebug("Detected tournament over.")
          ui.tournamentOver
          logic.onTournamentOver(m => send(myId, m))
      }
    }
    playGames()
    rdebug("State machine exiting main.")
  }

  def playGame(cfgs: NewGameConfigs) { 
    
    ui.initialize(cfgs)
    var livingOpponents = cfgs.opponents
    def numStillAlive = livingOpponents.size + 1

    logic.onInit(cfgs, myId, uiId, myManagerId)
    
    //===Wait for ships from UI, and place on board

    for((bow, stern) <- ui.placeShips) {
      logic.onPlaceShip(bow, stern)
    }

    //===Synchronize with opponents

    def synchronizeWith(toSynch: List[RPCProxy with BSSMInterface]) = {
      def waitFor(waitingOn: List[RPCProxy with BSSMInterface]) {
        if (!waitingOn.isEmpty) {
          respondToCall {
            case (from, 'synch, _) => waitFor(waitingOn.filter(_.id != from))
          }
        }
      }
      
      val thread = fork { waitFor(toSynch) }
      toSynch.foreach { _.synch }
    }
      
    def synchronizeOpps() = synchronizeWith(livingOpponents.map(o => statemach(o)))
    synchronizeOpps()

    //===Main game loop
    while(logic.isGameLoopRunning && (numStillAlive != 1)) {

      //debug("=====Game loop!")

      //===Request move from UI, fire at target, announce move made
      val myAttack = ui.requestAction match {
        case (target: Id, coord: Point) => (coord, target)
      }

      val getAttackHandler = fork {
        while (true) {
          respondToCall { 
            case (_, 'getAttack, _) => myAttack
          }
        }
      }

      synchronizeOpps()

      //===Handle queued FireAts from enemies; announce defeat

      for (opp <- livingOpponents;
           (coord, target) = statemach(opp).getAttack) {
        if (target == myId) 
          logic.onFireAtMe(opp, coord)
      }
      
      // perspective => List[(attacker, coord, result)]
      var fireResults: Map[Id, List[(Id, Point, FireResultType)]] = Map().withDefaultValue(Nil)
      // perspective => (hit, sunk, defeat, isWinner)
      var playerResults: Map[Id, (Int, Int, Int, Boolean)] = Map()

      logic.onHandleAttacks((to, fres) => fireResults += to -> (fres :: fireResults(to)),
          (to, pres) => playerResults += to -> pres)

      playerResults.get(myManagerId) match {
        case Some(pres) => myManager.sendPlayerResult(myId, pres._1, pres._2, pres._3, pres._4)
        case None =>
      }

      synchronizeOpps()

      getAttackHandler.kill()

      //===Forward FireResults to UI and tally score, queueing PlayerResults

      val fireResultHandler = fork {
        while (true) {
          respondToCall { 
            case (from, 'getFireResults, _) => 
              fireResults(from)
            case (from, 'getPlayerResult, _) =>
              playerResults.get(from)
          }
        }
      }

      for (opp <- myId :: livingOpponents;
           (attacker, coord, result) <- statemach(opp).getFireResults) {
        ui.sendFireResult(attacker, opp, coord, result)
        logic.onFireResult(attacker, opp, coord, result) 
      }
      
      var livingOpponentsNextTurn = livingOpponents
      for (opp <- myId :: livingOpponents;
           (hits, sunks, kills, isWinner) <- statemach(opp).getPlayerResult) {
        rdebug("Informed ui about " + opp)
        ui.sendPlayerResult(opp, hits, sunks, kills, isWinner)
        if (!isWinner) {
          livingOpponentsNextTurn = livingOpponentsNextTurn.filter(_ != opp)
        }
      }

      synchronizeOpps()
      livingOpponents = livingOpponentsNextTurn
      fireResultHandler.kill()

      //===Announce victory
      //isGameLoopRunning is proxy for isDefeated
      if (logic.isGameLoopRunning&&(numStillAlive == 1)) {
        logic.onLastAlive {
          //announce to others too?
          (recip: Id, pres: (Int, Int, Int, Boolean)) => {
            if (recip == myId) {
              ui.sendPlayerResult(myId, pres._1, pres._2, pres._3, pres._4)
            }
            if (recip == myManagerId) {
              myManager.sendPlayerResult(myId, pres._1, pres._2, pres._3, pres._4)
            }
          }
        }
        rdebug("End I won loop.")
      }

    }// end game loop

    //===Wait for GameOver from coordinator

    myManager.waitGameOver

    logic.onGameOver(m => send(myId, m)) // send pr message
    ui.gameOver

  }//end actor
}//end class
